#include "../include/defs.h"
#include "../include/memoryPaging.h"
#include "../include/kasm.h"
#include "../include/kc.h"

#define PAGING_STRUCTURE_SIZE 0x400000	//4 mega para los directorios y tablas

#define PAGE_SIZE 0x1000
#define PHYSICAL_PAGE_SIZE (PAGE_SIZE*2)

typedef int * pageDir;
//comienzo del directorio de paginas
static pageDir pageDirectory = (pageDir)(KERNEL_MEM_SIZE - PAGE_SIZE);

#define PAGE_PRESENT		3	//(011), supervisor, read/write, present
#define PAGE_NOT_PRESENT	2	//(010), supervisor, read/write, not present

#define MARK_PRESENT(x)		(((int)x & 0xFFFFFFFA) | PAGE_PRESENT)
#define MARK_NOT_PRESENT(x)	(((int)x & 0xFFFFFFFA) | PAGE_NOT_PRESENT)


//stack de paginas, para saber que direcciones no estan ocupadas
typedef struct {
	char ** pages;
	int maxCant;
	int top;
}pagingStack;

static pagingStack pageStack;

#define PAGES_STACK_SIZE 0x80000000 //2 gigas para las paginas en el stack
#define MAX_PAGES (PAGES_STACK_SIZE/PAGE_SIZE) //cantidad maxima de paginas

//cantidad maxima de paginas fisicas, es decir, de direcciones fisicas
//que voy a poder entregar
#define MAX_PHYSICAL_PAGES (PAGES_STACK_SIZE/PHYSICAL_PAGE_SIZE)


static void * popPage();
static void pushPage(char * page);
static void buildPagingStructure();
static void mapPage(void * physicalAddr);
static void unmapPage(void * physicalAddr);

void
initPaging() {
	int i;

	//inicializo el stack de paginas
	pageStack.pages = (char **)(KERNEL_MEM_SIZE + PAGING_STRUCTURE_SIZE);
	pageStack.top = 0;
	pageStack.maxCant = MAX_PHYSICAL_PAGES;

	//lleno el stack de paginas con direcciones de paginas fisicas
	char * pageDir = (char*)pageStack.pages + MAX_PHYSICAL_PAGES * sizeof(char*);
	for (i = 0; i < pageStack.maxCant; i++) {
		pageStack.pages[i] = pageDir;
		pageDir += PHYSICAL_PAGE_SIZE;
	}

	buildPagingStructure();
}

static void
buildPagingStructure() {
	int i, j;
	pageDir pageTable = (pageDir)KERNEL_MEM_SIZE;
	char * physicalAddr = 0;

	//mapeo el kernel y el espacio de la estructura de paginacion
	for (i = 0; i < 2; i++) {
		pageDirectory[i] = MARK_PRESENT(pageTable);

		for (j = 0; j < 1024; j++) {
			*pageTable = MARK_PRESENT(physicalAddr);
			physicalAddr += PAGE_SIZE;
			pageTable++;
		}
	}

	//mapeo el stack de paginas
	pageDirectory[i++] = MARK_PRESENT(pageTable);

	int stackPageSize = MAX_PHYSICAL_PAGES * sizeof(char*);
	//para mapear el stack entero tengo que iterar stackPageSize/PAGE_SIZE
	//veces, ya que cada entrada mapea el tamaño de una pagina
	for (j = 0; j < stackPageSize/PAGE_SIZE; j++) {
		*pageTable = MARK_PRESENT(physicalAddr);
		physicalAddr += PAGE_SIZE;
		pageTable++;
	}

	//seteo todas las paginas que no use como no presentes
	for (; j < 1024; j++) {
		*pageTable = 2;
		physicalAddr += PAGE_SIZE;
		pageTable++;
	}

	for (; i < 1024; i++) {
		pageDirectory[i] = MARK_PRESENT(pageTable);

		for (j = 0; j < 1024; j++) {
			*pageTable = 2;
			physicalAddr += PAGE_SIZE;
			pageTable++;
		}
	}

	//guardo la direccion del directorio en cr3
	_write_cr3(pageDirectory);
	//enciendo el flag de paginacion en cr0
	_write_cr0(_read_cr0() | 0x80000000);

}

void *
pageMalloc(int * size) {
	void * page = popPage();
	if (page != NULL) {
		if (size != NULL)
			*size = PHYSICAL_PAGE_SIZE;
		activatePage(page);
	}

	return page;
}

void
pageFree(void * page) {
	if (page != NULL) {
		deactivatePage(page);
		pushPage(page);
	}
}

void
activatePage(void * page) {
	int i;
	if (page == NULL)
		return;

	//una pagina fisica va a ser mas grande que una pagina de la paginacion,
	//asi que por cada pagina fisica tengo que activar la cantidad de paginas
	//que entren en una pagina fisica: PHYSICAL_PAGE_SIZE/PAGE_SIZE.
	for (i = 0; i < PHYSICAL_PAGE_SIZE/PAGE_SIZE; i++) {
		mapPage(page + i * PAGE_SIZE);
	}
	//escribo en cr3 para que el procesador use mi tabla actualizada, y no
	//la version cacheada que tiene, que esta desactualizada
//	_write_cr3(pageDirectory);
}

void
deactivatePage(void * page) {
	int i;
	if (page == NULL)
		return;

	for (i = 0; i < PHYSICAL_PAGE_SIZE/PAGE_SIZE; i++) {
		unmapPage(page + i * PAGE_SIZE);
	}
	//escribo en cr3 para que el procesador use mi tabla actualizada, y no
	//la version cacheada que tiene, que esta desactualizada
//	_write_cr3(pageDirectory);
}

static void
mapPage(void * physicalAddr)
{
	//tomo los 10 bytes de la izquierda
	int dirIdx = (int)physicalAddr >> 22;
	//tomo los 10 bytes del medio
	int tableIdx = (int)physicalAddr >> 12 & 0x03FF;

	//seteo los ultimos 3 bits en 0
	pageDir pageTable = (pageDir)(pageDirectory[dirIdx] & 0xFFFFF000);

	pageDirectory[dirIdx] = MARK_PRESENT(pageDirectory[dirIdx]);
	pageTable[tableIdx] = MARK_PRESENT(physicalAddr);
}

static void
unmapPage(void * physicalAddr)
{
	//tomo los 10 bytes de la izquierda
	int dirIdx = (int)physicalAddr >> 22;
	//tomo los 10 bytes del medio
	int tableIdx = (int)physicalAddr >> 12 & 0x03FF;

	pageDir pageTable = (pageDir)(pageDirectory[dirIdx] & 0xFFFFF000);

	pageTable[tableIdx] = MARK_NOT_PRESENT(physicalAddr);
}

static void *
popPage() {
	if (pageStack.top == pageStack.maxCant)
		return NULL;

	return pageStack.pages[pageStack.top++];
}

static void
pushPage(char * page) {
	if (pageStack.top == 0)
		return;
	pageStack.pages[--pageStack.top] = page;
}

